Deleting items
If shoppers on our site change their minds about items they have added to their carts, they can change the Quantity field for those items to 0. We can provide a more reassuring behavior, though, by adding explicit Delete buttons for each item. The actual effect of the button can be the same as changing the Quantity field, but the visual feedback can reinforce the fact that the item will not be purchased.
First, we need to add
the new buttons. Since they won't function without JavaScript, we won't
put them in the HTML. Instead, we'll let jQuery add them to each row:
$('<th> </th>')
.insertAfter('#cart thead th:nth-child(2)');
$('#cart tbody tr').each(function() {
$deleteButton = $('<img />').attr({
'width': '16',
'height': '16',
'src': '../images/cross.png',
'alt': 'remove from cart',
'title': 'remove from cart',
'class': 'clickable'
});
$('<td></td>')
.insertAfter($('td:nth-child(2)', this))
.append($deleteButton);
});
$('<td> </td>')
.insertAfter('#cart tfoot td:nth-child(2)');
We need to create empty cells in
the header and footer rows as placeholders so that the columns of the
table still line up correctly. The buttons are created and added on the
body rows only:
Now we need to make the buttons do something. We can change the button definition to add a click handler:
$deleteButton = $('<img />').attr({
'width': '16',
'height': '16',
'src': '../images/cross.png',
'alt': 'remove from cart',
'title': 'remove from cart',
'class': 'clickable'
}).click(function() {
$(this).parents('tr').find('td.quantity input')
.val(0);
});
The handler finds the quantity field in the same row as the button, and sets the value to 0. Now, the field is updated, but the calculations are out of sync:
We need to trigger the calculation as if the user had manually changed the field value:
$deleteButton = $('<img />').attr({
'width': '16',
'height': '16',
'src': '../images/cross.png',
'alt': 'remove from cart',
'title': 'remove from cart',
'class': 'clickable'
}).click(function() {
$(this).parents('tr').find('td.quantity input')
.val(0).trigger('change');
});
Now the totals update when the button is clicked:
Now for the visual feedback. We'll hide the row that was just clicked, so that the item is clearly removed from the cart:
$deleteButton = $('<img />').attr({
'width': '16',
'height': '16',
'src': '../images/cross.png',
'alt': 'remove from cart',
'title': 'remove from cart',
'class': 'clickable'
}).click(function() {
$(this).parents('tr').find('td.quantity input')
.val(0).trigger('change')
.end().hide();
});
While the row is hidden, the
field is still present on the form. This means it will be submitted with
the rest of the form, and the item will be removed on the server side
at that time.
Our row striping has
been disturbed by the removal of this row. To correct this, we move our
existing striping code into a function so that we can call it again
later. At the same time, we need to modify the code to ensure that our
alternating row selection ignores any hidden rows. Unfortunately, even
if we filter out all of the hidden rows, we still can't use the :nth-child(even) selector, because it will apply the alt
class to all visible rows that are the "eventh" child of their parent.
Let's see what happens when we apply the following modified code to four
table rows when the second one is hidden:
$('#cart tbody tr').removeClass('alt')
.filter(':visible:nth-child(even)').addClass('alt');
We get the following markup [condensed]:
<tr> . . . </tr>
<tr style="display: none"> . . . </tr>
<tr> . . . </tr>
<tr class="alt"> . . . </tr>
Three rows are visible; only the fourth has the alt class applied to it. What we need is the :visible:odd
selector expression, since it will choose every other row after
removing the hidden ones for the selection (and it accounts for the
shift from a one-indexed to a zero-indexed selector). Using :odd or :even could produce unexpected results if we had more than one<tbody> element, but in this case we're in good shape. With the selector change in place, our new function looks like this:
var stripe = function() {
$('#cart tbody tr').removeClass('alt')
.filter(':visible:odd').addClass('alt');
};
stripe();
Now we can call this function again after removing a row:
$deleteButton = $('<img />').attr({
'width': '16',
'height': '16',
'src': '../images/cross.png',
'alt': 'remove from cart',
'title': 'remove from cart',
'class': 'clickable'
}).click(function() {
$(this).parents('tr').find('td.quantity input')
.val(0).trigger('change')
.end().hide();
stripe();
});
The deleted row has now seamlessly disappeared:
This completes yet
another enhancement using jQuery that is completely transparent to the
code on the server. As far as the server is concerned, the user just
typed a 0 in the input field, but to the user this is a distinct removal operation that is different from changing a quantity.